package evaluator;

import game.Cell;
import game.HexBoard;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import players.Player;

public class ElectricalResistanceFinder {

  private static final double ZERO = .01;
  private static final double ONE = 10000;
  private static final double INFINITY = 1000000000;
  private static final double VCC = 1000;

  
  private final HexBoard board; 
  private final Player player, otherPlayer, leftRightPlayer, topBottomPlayer;

  private final double[] nodes;
  private final int totalNodes, dim;
  
  /* Keeps a list of nodes <Value> that are virtually connected to a <Key> node */ 
  private final Map<Integer, List<Integer>> virtualConnectionMap = new HashMap<Integer, List<Integer>>() ;
  
  /** Finds the electrical resistance for leftRightPlayer. */
  public ElectricalResistanceFinder(HexBoard bd, Player player) {
    this.player = player ; 
    this.otherPlayer = bd.getOtherPlayer(player) ;   
    
    leftRightPlayer = bd.getLeftRightPlayer() ;
    topBottomPlayer = bd.getTopBottomPlayer() ;

    board = bd; 
    dim = bd.getSize();
    totalNodes = dim * dim + 2;
    nodes = new double[totalNodes];
    creatNodes();
  }


  ///////////// VC 
  public void addVirtualConnection(Cell cell1, Cell cell2) {
	
	int n1 = nodeNumber(cell1) ;
	int n2 = nodeNumber(cell2) ;

	  System.out.println("Trying to make a virtual connection between: " + 
	          n1 +"("+ cell1.getPlayer()+") and " + n2+"("+cell2.getPlayer()+")" );
	  if (cell1.getPlayer()!=cell2.getPlayer()){
		  System.err.println("Cannot make a virtual connection between nodes from different players") ;
		  return ;
	  }

	  if (board.isEmpty(cell1) ) { //HexBoard.is_EMPTY_CELL(cell1) ){
		  System.err.println("Cannot make a virtual connection for an empty cell") ;
		  return ;
	  }
	  
	if (virtualConnectionMap.get(n1) == null)
		virtualConnectionMap.put(n1, new ArrayList<Integer>()) ;
	
	if (virtualConnectionMap.get(n2) == null)
		virtualConnectionMap.put(n2, new ArrayList<Integer>()) ;
	
	virtualConnectionMap.get(n1).add(n2);
	virtualConnectionMap.get(n2).add(n1);
	
	System.out.println("Adding VC for " + n1 + " and " + n2);
	
  }

  
  private void connectToEdge(Cell cell, int count) {
    if (cell.getPlayer()!= this.player)
		  System.err.println("Cannot connect a cell from the other player to the edge.") ;
		
	  if (virtualConnectionMap.get(nodeNumber(cell)) == null)
			virtualConnectionMap.put(nodeNumber(cell), new ArrayList<Integer>()) ;
	  
	  if (virtualConnectionMap.get(count) == null)
			virtualConnectionMap.put(count, new ArrayList<Integer>()) ;
	  
	  virtualConnectionMap.get(nodeNumber(cell)).add(count) ;
	  virtualConnectionMap.get(count).add(nodeNumber(cell)) ;
  }
  
  
  public void connectToTopOrLeft(Cell cell)
  {
	  connectToEdge(cell, 0);
  }


  public void connectToBottomOrRight(Cell cell)
  {
    connectToEdge(cell, totalNodes - 1);	  
  }
  
   
  private void creatNodes() {

    for (int y = 0; y < dim; y++)
      for (int x = 0; x < dim; x++) {
        if (board.getCell(x, y).getPlayer() == this.player)
          nodes[1 + x + y * board.getSize()] = ZERO;
        else if (board.getCell(x, y).getPlayer() == this.otherPlayer)
          nodes[1 + x + y * board.getSize()] = INFINITY;
        else
          nodes[1 + x + y * board.getSize()] = ONE;
      }
    nodes[0] = ZERO;
    nodes[totalNodes - 1] = ZERO;

  }

  private double getResistance(int n, int m) {
	if (virtualConnectionMap.get(n) != null && virtualConnectionMap.get(n).contains(m) ){
		return ZERO + ZERO;
	}
	
    return (n == 0 || m == 0 || n == totalNodes - 1 || m == totalNodes - 1) ? ZERO
        : nodes[n] + nodes[m];
  }

  private List<Integer> getNeighbors(int n) {

    List<Integer> result = new ArrayList<Integer>();

    if (n - dim > 0)
      result.add(n - dim); // Top left node

    if (n - dim + 1 > 0 && n % dim != 0)
      result.add(n - dim + 1); // Top right

    if (n % dim != 0)
      result.add(n + 1);
    
    if (n + dim < totalNodes - 1)
      result.add(n + dim);

    if (n + dim - 1 < totalNodes - 1 && (n - 1) % dim != 0)
      result.add(n + dim - 1);

    if ((n - 1) % dim != 0)
      result.add(n - 1);
    
    if(n % dim == 0 && player == leftRightPlayer)
        result.add(totalNodes - 1); // Right Edge for leftRightPlayer

    if ((n - 1) % dim == 0 && player == leftRightPlayer)
      result.add(0); // Left Edge for leftRightPlayer
    
    if (n <= dim && player == topBottomPlayer) 
    	result.add(0) ; // Top edge for topBottomPlayer
    
    if (n > dim * (dim - 1) && player == topBottomPlayer)
    	result.add(totalNodes - 1) ; // Bottom edge for topBottomPlayer

    if (virtualConnectionMap.get(n) != null )
    	for (int vn : virtualConnectionMap.get(n)) {
    		result.add(vn);
    	}
    return result;
  }

  private List<Integer> getZeroPoleNodes() {

    List<Integer> result = new ArrayList<Integer>();
    if (player == leftRightPlayer)
    	for (int i = 0; i < dim; i++)
    		result.add(i * dim + dim);
    else if (player == topBottomPlayer)
    	for (int i = 0; i < dim; i++)
    		result.add(dim * (dim - 1) + i + 1 );
    
    if (virtualConnectionMap.get(totalNodes - 1 ) != null)
    	for (int n : virtualConnectionMap.get(totalNodes - 1 ))
    		result.add(n) ;
    
    return result;
  }

	private double[][] calculateMatrix() {
		double[][] result = new double[totalNodes][totalNodes];
		for (int i = 0; i < totalNodes; i++) {
			result[i] = calculateMatrixRow(i);
		}
		return result;
	}

	public double getEqResistance() {

		double[] voltages = calculateVoltages();
		double totalCurrent = 0;
		for (Integer node : getZeroPoleNodes())
			totalCurrent += voltages[node] / ZERO;

		return VCC / totalCurrent;
	}

	private double[] calculateMatrixRow(int row) {
		double[] result = new double[totalNodes];

		if (row == 0 || row == totalNodes - 1) {
			result[row] = 1;
			return result;
		}

		List<Integer> neighbors = getNeighbors(row);
		double vRowCoef = 0;

		for (Integer neighbor : neighbors) {
			vRowCoef += 1 / getResistance(row, neighbor);
			result[neighbor] += -1 / getResistance(row, neighbor);
		}

		result[row] = vRowCoef;

		return result;
	}

	private double[] getBMatrix() {
		double[] result = new double[totalNodes];
		result[0] = VCC;
		return result;
	}

	private double[] calculateVoltages() {
		return GaussianElimination.lsolve(calculateMatrix(), getBMatrix());
	}

	private int nodeNumber(Cell cell) {
		return cell.getX() + cell.getY() * dim + 1;
	}

}
